from com import skype

from java.awt import SystemTray
from java.awt import Image, Toolkit
from java.awt import PopupMenu, MenuItem, TrayIcon
from java.awt import AWTException
from java.awt.event import ActionListener

import sys
import re

from sj.utils import *
from sj.utils import _capture, _count_lines

ACTIVE_STATUSES = [skype.Chat.Status.DIALOG,
                   skype.Chat.Status.LEGACY_DIALOG,
                   skype.Chat.Status.MULTI_SUBSCRIBED]

PLUGIN_ID_PATTERN = re.compile(r'\[([a-zA-Z0-9_\-]*)\]')

def validatePluginId(pluginId):
    match = PLUGIN_ID_PATTERN.match(pluginId)
    if match is None:
        return ''

    return match.groups()[0]

def importModuleNamespace(module_name):
    module = __import__(module_name, globals(), locals(), [''])
    return module.__dict__

def resolveGlobalsFromModule(module_name):
    if module_name == '--':
        return {}
    elif module_name:
        return importModuleNamespace(module_name)

    return globals()

_command_table = {}

def registerCommand(function, *namelist):
    for name in namelist:
        _command_table[name] = function

def parseCommand(command):
    command = command.lstrip()
    if not command:
        return ('', '')

    if not command[0].isalpha():
        return (command[0], command[1:])

    command = command.split(' ', 1)
    if len(command) == 2:
        return command

    return (command[0], '')

def findCommand(command):
    (command, argstr) = parseCommand(command)
    return (_command_table.get(command.lower()), command, argstr)

class CommandEvent:
    def __init__(self, commandName, argstr = ''):
        self.commandName = commandName
        self.argstr = argstr

        self.args = argstr.split()
        self.larg = self.args and self.args[0].lower() or ''

def doBangCommand(shell, event):
    'Magical command makes special non-alphanumerics work.'

    commandName = event.larg
    if commandName:
        commandName = event.commandName + commandName
        action = _command_table.get(commandName)
        if callable(action):
            argstr = event.argstr.lstrip()[len(event.larg):]
            return action(shell, CommandEvent(commandName, argstr))

def doPythonCommand(shell, event):
    shell.processPythonCommand(event.argstr.lstrip())
def doQuitShell(shell, event):
    shell.quitShell()
def doEcho(shell, event):
    shell.sendMessage(event.argstr.lstrip(), type = 'cash')
def doChatSessionWindows(shell, event):
    shell.sendMessage(getChatList())
def doShowConnectorEvents(shell, event):
    shell.sendMessage('\n'.join([ev.message for ev in self.events]),
                                type = 'cash')

def doInteractiveShell(shell, event):
    shell.interactiveShell()
def doInterplug(shell, event):
    result = shell.processInterplug(event.args)
    if isinstance(result, basestring):
        shell.sendMessage(result, type = 'mp')
def doRawConnectorCommand(shell, event):
    c = skype.connector.Connector.getInstance()
    c.execute(shell.argstr.lstrip())

def doHelp(shell, event):
    for command in event.args:
        action = _command_table.get(command.lower())
        name = getattr(action, '__name__', '')
        doc = getattr(action, '__doc__', '')

        shell.sendMessage('%s - %s\n%s' % (name, command, doc))
    else:
        shell.sendMessage(', '.join(_command_table.keys()))

registerCommand(doBangCommand        , '!') # '#', '%', '$'
registerCommand(doPythonCommand      , ';', '!eval')
registerCommand(doQuitShell          , '!quit')
registerCommand(doEcho               , '!echo')
registerCommand(doChatSessionWindows , '!windows')
registerCommand(doShowConnectorEvents, '!events')
registerCommand(doInteractiveShell   , '!interactive-shell')
registerCommand(doInterplug          , '!inter-plug')
registerCommand(doRawConnectorCommand, '!raw')
registerCommand(doHelp               , '!help')

class SkypeJython:
    class JythonMessageAdapter(skype.ChatMessageAdapter):
        def __init__(self, shell):
            self.shell = shell

        def chatMessageSent(self, sent):
            if sent.getChat().equals(self.shell.chat):
                self.shell.incomingMessage(sent.getContent())

        def chatMessageReceived(self, sent):
            if sent.getChat().equals(self.shell.chat):
                self.shell.incomingMessage(sent.getContent())

    class ConnectorAdapter(skype.connector.AbstractConnectorListener):
        def __init__(self, shell):
            self.shell = shell

        def messageReceived(self, message): # ConnectorMessageEvent
            # self.shell.log('Received-Message: %r' % message)
            self.shell.addEvent(message)
        def messageSent(self, message): # ConnectorMessageEvent
            # self.shell.log('Message-Sent: %r' % message)
            self.shell.addEvent(message)
        def statusChanged(self, status): # ConnectorStatusEvent
            # self.shell.log('Status-Changed: %r' % status)
            self.shell.addEvent(status)

    def __init__(self, options):
        self.events = []

        self.plugin_id = validatePluginId('[%s]' % options.plugin_id)
        self.plugin_actions = {}

        self.ns_locals = {}
        self.ns_globals = resolveGlobalsFromModule(options.globals_module)

        self.messaging = False

        self.chat = self.getChatSession(options)
        self.chat.open()

        adapter = self.JythonMessageAdapter(self)
        skype.Skype.addChatMessageListener(adapter)

        if options.store_connector_events:
            adapter = self.ConnectorAdapter(self)
            connector = skype.connector.Connector.getInstance()
            connector.addConnectorListener(adapter)

        self.sendMessage('%s Connected' % self.__class__.__name__,
                         type = 'handshake')

    def addEvent(self, object):
        self.events.append(object)
    def lastEventMessage(self):
        return self.events[-1].message

    def log(self, message):
        print message

    def getChatSession(self, options):
        if options.user_id:
            return skype.Skype.chat(options.user_id)

        topic = options.topic
        if topic:
            # Try to find a topic already existing:
            for chat in skype.Skype.getAllChats():
                # XXX What if windowTitle is different??
                # And topic is write-only!!
                if chat.status not in ACTIVE_STATUSES:
                    continue
                if chat.windowTitle == topic:
                    return chat

            return skype.Skype.chat(topic)

    def quitShell(self):
        self.sendMessage('Quitting %s' % self.__class__.__name__,
                         type = 'bye')

        from sys import exit
        exit(0)

    def sendMessage(self, message, type = None):
        # XXX synchronized!
        self.messaging += 1
        if type is not None:
            message = '(%s) %s' % (type, message)

        self.chat.send(message)

    def incomingMessage(self, message):
        # XXX synchronized!
        if not self.messaging:
            self.parseCommand(message)
        else:
            self.messaging -= 1

    def parseCommand(self, command):
        (action, command, argstr) = findCommand(command)
        if callable(action):
            action(self, CommandEvent(command, argstr))

    def processInterplug(self, args):
        if not self.plugin_id or not len(args):
            return

        directive = args[0]
        if directive == 'load-module':
            if len(args) != 2:
                return 'Expected 1 argument naming module, not %r' % ' '.join(args[1:])

            try: self.plugin_actions = importModuleNamespace(args[1])
            except Exception, e:
                return '%s: %s' % (e.__class__.__name__, e)

            return 'Loaded Inter-Plug Module: %r' % args[1]

        plugin_id = validatePluginId(directive)
        if plugin_id == self.plugin_id:
            command = args[1]
            args = args[2:]

            action = self.plugin_actions.get(command)
            if callable(action) and getattr(action, 'pluggable', False):
                return action(self, *args)

    def interactiveShell(self):
        from sj.utils import MyExamine
        MyExamine() # XXX Do this in another thread if not already running!

    def processPythonCommand(self, source):
        input_type = 'single'
        if '\n' in source: # or _count_lines(source) > 1
            input_type = 'exec'

        (success, result) = self.evaluatePython(source, input_type = input_type)
        if success:
            (result, output) = result
            if output:
                self.sendMessage(str(output), type = 'cash')

            if result is not None:
                self.sendMessage(str(result), type = 'cash')
        else:
            self.sendMessage(str(result), type = 'bug')

    def evaluatePython(self, source, input_type = 'single'):
        try: code = compile(source, '<skype conversation>', input_type)
        except SyntaxError, e:
            (etype, value, tb) = sys.exc_info()
            return (False, '%s: %s' % (etype, value))

        def execute_python(code, locals, globals):
            if input_type in ('eval', 'single'):
                return eval(code, globals, locals)
            else:
                exec code in globals, locals

        ns_locals = self.getLocalNamespace()
        ns_globals = self.getGlobalNamespace()

        try: result = _capture(execute_python, code, ns_locals, ns_globals)
        except Exception, e:
            output = str(e)
            return (False, output)

        return (True, result)

    def getLocalNamespace(self):
        self.ns_locals['shell'] = self
        return self.ns_locals
    def getGlobalNamespace(self):
        return self.ns_globals

    class SystrayActionListener(ActionListener):
        def __init__(self, shell):
            self.shell = shell

        def actionPerformed(self, event):
            try: self.shell.parseCommand(event.getActionCommand())
            except skype.SkypeException, se:
                self.shell.log('SkypeException while parsing command %s: %s' % \
                               (event.getActionCommand(), se))
                se.printStackTrace()
                # sys.exit(1)

    def getResourceURLContent(self, name, ResourceClass):
        # XXX ResourceURLContent hasn't been ported.
        raise NotImplementedError

        data = ResourceURLContent(ResourceClass, name)
        return data.getURL()

    SYSTRAY_IMAGE = 'resources/icon/skypejython.png'

    def setupSystemTray(self, ResourceClass):
        if SystemTray.isSupported():
            url = self.getResourceURLContent(self.SYSTRAY_IMAGE, ResourceClass)
            image = Toolkit.getDefaultToolkit().getImage(url)

            listener = JythonShell.SystrayActionListener(self)
            popup = PopupMenu()

            item = MenuItem('Quit')
            item.setActionCommand('!quit')
            item.addActionListener(listener)
            popup.add(item)

            trayIcon = TrayIcon(image, 'SkypeJython', popup)
            trayIcon.addActionListener(listener)

            tray = SystemTray.getSystemTray()

            try: tray.add(trayIcon)
            except AWTException, e:
                self.log('System tray icon not available.')
